/**     
 * @Title: DefaultKeyGenerator.java   
 * @Package me.mq.util   
 * @Description: TODO
 * @author weiwei 
 * @date 2018年5月30日 上午11:47:20   
 * @version V1.0     
 */
package me.mq.util;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

import lombok.extern.slf4j.Slf4j;

import org.springframework.stereotype.Component;

import com.google.common.base.Preconditions;

/**
 * @ClassName: DefaultKeyGenerator
 * @Description: TODO
 * @author weiwei
 * @date 2018年5月30日 上午11:47:20
 * 
 */
@Component
@Slf4j
public class DefaultKeyGenerator {

	public static final long EPOCH;

	private static final long SEQUENCE_BITS = 12L;

	private static final long WORKER_ID_BITS = 10L;

	private static final long SEQUENCE_MASK = (1 << SEQUENCE_BITS) - 1;

	private static final long WORKER_ID_LEFT_SHIFT_BITS = SEQUENCE_BITS;

	private static final long TIMESTAMP_LEFT_SHIFT_BITS = WORKER_ID_LEFT_SHIFT_BITS + WORKER_ID_BITS;

	private static final long WORKER_ID_MAX_VALUE = 1L << WORKER_ID_BITS;

	private long workerId = 0;

	static {
		Calendar calendar = Calendar.getInstance();
		calendar.set(2016, Calendar.NOVEMBER, 1);
		calendar.set(Calendar.HOUR_OF_DAY, 0);
		calendar.set(Calendar.MINUTE, 0);
		calendar.set(Calendar.SECOND, 0);
		calendar.set(Calendar.MILLISECOND, 0);
		EPOCH = calendar.getTimeInMillis();
	}

	private long sequence;

	private long lastTime;

	public DefaultKeyGenerator() {

	}

	public DefaultKeyGenerator(long workerId) {
		Preconditions.checkArgument(workerId >= 0L && workerId < WORKER_ID_MAX_VALUE);
		this.workerId = workerId;
	}

	/**
	 * Generate key.
	 *
	 * @return key type is @{@link Long}.
	 */
	public synchronized long generateKey() {
		long currentMillis = getCurrentMillis();
		Preconditions.checkState(lastTime <= currentMillis, "Clock is moving backwards, last time is %d milliseconds, current time is %d milliseconds", lastTime, currentMillis);
		if (lastTime == currentMillis) {
			if (0L == (sequence = ++sequence & SEQUENCE_MASK)) {
				currentMillis = waitUntilNextTime(currentMillis);
			}
		} else {
			sequence = 0;
		}
		lastTime = currentMillis;
		if (log.isDebugEnabled()) {
			log.debug("{}-{}-{}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date(lastTime)), workerId, sequence);
		}
		return ((currentMillis - EPOCH) << TIMESTAMP_LEFT_SHIFT_BITS) | (workerId << WORKER_ID_LEFT_SHIFT_BITS) | sequence;
	}

	private long waitUntilNextTime(final long lastTime) {
		long time = getCurrentMillis();
		while (time <= lastTime) {
			time = getCurrentMillis();
		}
		return time;
	}

	/**
	 * Get current millis.
	 *
	 * @return current millis
	 */
	private long getCurrentMillis() {
		return System.currentTimeMillis();
	}
}
